using System;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Xml;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Media;
using System.IO;


namespace PokerTimer
{
  
    public partial class PokerTimer : Form
    {
        private List<Blind> blindSystem = new List<Blind>();
        private int currentBlindIndex;
        private enum State { WAITING, STOPPED, RUNNING, BLINDCHANGE, LASTBLIND };
		private State currentState = State.WAITING;
		//current time remaining
		private TimeSpan timeRemaining;
        //duration when start button is pressed
        private TimeSpan startDuration;
        //time the clock was (re)started
        private DateTime startTime;
        Settings settings = new Settings();

        const int POWER_STATE_ON = 0x10000;
        const int POWER_STATE_OFF = 0x20000;
        const int POWER_STATE_SUSPEND = 0x200000;
        const int POWER_FORCE = 4096;

        //application full name and path
        private string fullPath = Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName;

        private SoundPlayer soundPlayer;

        private bool willWakeUp;


        public PokerTimer()
        {
            InitializeComponent();
            //pictureBox1.Width = Screen.PrimaryScreen.Bounds.Width;
            //pictureBox1.Height = Screen.PrimaryScreen.Bounds.Height;

            soundPlayer = new SoundPlayer(Path.GetDirectoryName(fullPath) + Path.DirectorySeparatorChar + "alarm_3.wav");
            soundPlayer.Load();

        }

        /// <summary>
        /// Open xml file with blinds system.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miOpenBlindsystem_Click(object sender, EventArgs e)
        {
            if( DialogResult.OK == openFileDialog.ShowDialog() ) 
            {
                XmlDocument xml = new XmlDocument();
                xml.Load(openFileDialog.FileName);
                XmlNodeList blinds = xml.DocumentElement.SelectNodes("blind");
                //only clear and replace current blind system if file contains blinds
                if (blinds.Count > 0)
                {
                    blindSystem.Clear();
                    foreach (XmlNode node in blinds)
                    {
                        Blind blind = new Blind();
                        blind.bigBlind = int.Parse(node.Attributes["bigblind"].Value);
                        blind.smallBlind = int.Parse(node.Attributes["smallblind"].Value);
                        blind.minutes = int.Parse(node.Attributes["minutes"].Value);
                        blind.seconds = int.Parse(node.Attributes["seconds"].Value);
                        blindSystem.Add(blind);
                    }
                    currentBlindIndex = 0;
                    NewBlind();
                    DisplayTime();
                    setState(State.STOPPED);
                    lblStatus.Text = "Loaded " + blindSystem.Count + " blinds.";
                }
            }
        }


    /// <summary>
		/// Must be called for a state change. Sets the timer and gui accordingly.
		/// </summary>
		/// <param name="newState"></param>
		private void setState(State newState) 
		{
    		currentState = newState;
			switch(currentState) 
			{
                case State.STOPPED:
                    timer.Enabled = false;
                    btnStart.Enabled = true;
					btnStop.Enabled = false;
					btnReset.Enabled = true;
//                    if( willWakeUp ) RunAppAtTime(fullPath, DateTime.MinValue );		//stop app wakeup
                    break;
				case State.RUNNING:
					timer.Enabled = true;
					btnStart.Enabled = false;
					btnStop.Enabled = true;
					btnReset.Enabled = false;
					break;
                case State.BLINDCHANGE:
                    SetSystemPowerState(null, POWER_STATE_ON, POWER_FORCE);	//power on if standby
                    BringToFront();
                    soundPlayer.Play();

                //if there are more blinds, restart clock
                    currentBlindIndex++;
                    NewBlind();
                    if (currentBlindIndex < blindSystem.Count - 1)
                    {
                        //pause or start clock at once?
                        if (settings.ckbPauseOnBlindChange.Checked)
                        {
                            setState(State.STOPPED);
                        }
                        else
                        {
                            StartClock();
                        }
                    }
                    else
                    {
                        setState(State.LASTBLIND);
                    }
                    break;
                case State.LASTBLIND:
//                    if (willWakeUp) RunAppAtTime(fullPath, DateTime.MinValue);		//stop app wakeup
                    timer.Enabled = false;
                    btnStart.Enabled = false;
                    btnStop.Enabled = false;
                    btnReset.Enabled = true;
                    timeRemaining = new TimeSpan();     //clear clock
                    DisplayTime();
                    lblStatus.Text = "Last blinds reached. Clock stopped.";
                    break;
            }

        }

    		/// <summary>
		/// Displays the current timer values on screen.
		/// </summary>
		private void DisplayTime() 
		{
            lblMinutes.Text = timeRemaining.Minutes.ToString();
			lblSeconds.Text = formatTimeUnit(timeRemaining.Seconds);
		}

        /// <summary>
        /// Update blinds display and set timer to blind value or overrided time value.
        /// </summary>
        private void NewBlind()
        {
            //use xml time og settings time
            int minutes = blindSystem[currentBlindIndex].minutes;
            int seconds = blindSystem[currentBlindIndex].seconds;
            if (settings.ckbOverrideTime.Checked ) {
                minutes = decimal.ToInt32(settings.numMinutes.Value);
                seconds = 0;
            }
            timeRemaining = new TimeSpan(0, minutes, seconds);
            lblSmallBlind.Text = blindSystem[currentBlindIndex].smallBlind.ToString();
            lblBigBlind.Text = blindSystem[currentBlindIndex].bigBlind.ToString();
        }

        /// <summary>
        /// Returns a string of two digits (or more) representing the time provided.
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        private string formatTimeUnit(int time)
        {
            if (time < 10)
            {
                return "0" + time;
            }
            else
            {
                return time.ToString();
            }
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            switch (currentState)
            {
                case State.RUNNING:
                    timeRemaining = startDuration - (DateTime.Now - startTime);
                    //check if alarm should go off
                    if (timeRemaining.TotalSeconds <= 0)
                    {
                        setState(State.BLINDCHANGE);
                    }
                    DisplayTime();
                    break;
            }
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            StartClock();
        }

        /// <summary>
        /// Set the initial time values and go to the running state
        /// </summary>
        private void StartClock()
        {
            startTime = DateTime.Now;
            startDuration = timeRemaining;		//continiue counting if paused

            //if more than 5 second until alarm, make sure app wakes up if pda turned off
            if (timeRemaining.TotalSeconds >= 5)
            {
                DateTime wakeupTime = startTime + timeRemaining - new TimeSpan(0, 0, 5); //wake up 5 seconds before alarm
                RunAppAtTime(fullPath, wakeupTime);		//app wakeup
                willWakeUp = true;
            }

            setState(State.RUNNING);
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            setState(State.STOPPED);
        }

        private void miExit_Click(object sender, EventArgs e)
        {
            Close();
        }

        /// <summary>   
        /// This function prompts the system to start running a specified application at a specified time.   
        /// </summary>   
        /// <param name="appName">Name of the application to be started.</param>   
        /// <param name="time">DateTime at which to run application.</param>
        /// <remarks>To cancel an existing RunAppATime request pass the application name and DateTime.MinValue</remarks>
        public static void RunAppAtTime(string appName, System.DateTime time)
        {
            byte[] timebytes;

            if (time == System.DateTime.MinValue)
            {
                //cancel request pass null
                timebytes = null;
            }
            else
            {
                //get native system time struct
                SystemTime st = SystemTime.FromDateTime(time);
                //get bytes
                timebytes = st.ToByteArray();
            }

            if (!CeRunAppAtTime(appName, timebytes))
            {
                throw new Exception("Cannot Set Notification Handler");
            }
        }
        
         /// <summary>
        /// Run app at a given time, even if in standby mode.
        /// </summary>
        /// <param name="pwszAppName"></param>
        /// <param name="lpTime"></param>
        /// <returns></returns>
        [DllImport("coredll.dll", EntryPoint = "CeRunAppAtTime", SetLastError = true)]
        private static extern bool CeRunAppAtTime(string pwszAppName, byte[] lpTime);

        /// <summary>
        /// Set the power state of device.
        /// </summary>
        /// <param name="psState">Names the desired system state to enter. If this parameter is not NULL then the StateFlags parameter is ignored.</param>
        /// <param name="stateFlags">
        /// POWER_STATE_ON	On state	
        /// POWER_STATE_OFF	Off state	
        /// POWER_STATE_CRITICAL	Critical state	
        /// POWER_STATE_BOOT	Boot state	
        /// POWER_STATE_IDLE	Idle state	
        /// POWER_STATE_SUSPEND	Suspend state	
        /// POWER_STATE_RESET	Reset state</param>
        /// <param name="options">Uses the optional POWER_FORCE flag to indicate that the state transfer is urgent.</param>
        /// <returns></returns>
        [DllImport("coredll.dll", EntryPoint = "SetSystemPowerState", SetLastError = true)]
        private static extern int SetSystemPowerState(string psState, int stateFlags, int options);

        private void miSettings_Click(object sender, EventArgs e)
        {
            settings.ShowDialog();
            //if override time requested, update time remaining
            if ( settings.ckbOverrideTime.Checked && currentState != State.RUNNING)
            {
                timeRemaining = new TimeSpan(0, decimal.ToInt32(settings.numMinutes.Value), 0);
                DisplayTime();
            }
        }

        private void btnReset_Click(object sender, EventArgs e)
        {
            currentBlindIndex = 0;
            NewBlind();
            DisplayTime();
        }
    }

    public class Blind
    {
        public int bigBlind;
        public int smallBlind;
        public int minutes;
        public int seconds;
    }

}